package ru.nosport.matrixaria.modules.core.rmh;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import ru.nosport.matrixaria.core.MessageHandlerException;
import ru.nosport.matrixaria.modules.common.AppServerProcess;


import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Random;

/**
 * User: vfabr
 * Date: 25.10.2006
 * Time: 13:16:14
 */

public class RMessageHandler implements AppServerProcess, RemoteMessageHandler {
	private final String serviceName = "RMessageHandler";
	private int portRmiRegistry;
	private int portRmiServer;

	private int retryCount;
	private int requestId = new Random().nextInt();


	/**
	 * <p>
	 * Creates RemoteMessageHandler module via configuration file.
	 * </p>
	 * Configuration entries:<br />
	 * rmiRegistryPort - Int default 1099<br />
	 * rmiServerPort - Int default random<br />
	 * retryCount - Int default 1<br />
	 *
	 * @param _conf - Configuration
	 */
	public RMessageHandler(Configuration _conf) {
		this(_conf.getInt("rmiRegistryPort", 1099),
				_conf.getInt("rmiServerPort", 0),
				_conf.getInt("retryCount", 1)
		);
	}

	/**
	 * Creates RemoteMessageHandler module.
	 *
	 * @param _registryPort - RMI portRmiRegistry number
	 * @param _serverPort - Port for RMessageHandler connections (<i><b>0</b> means random</i>)
	 * @param _retryCount - The number of attempts to call a remote method
	 */
	public RMessageHandler(int _registryPort, int _serverPort, int _retryCount) {
		this.portRmiRegistry = _registryPort;
		this.portRmiServer = _serverPort;

		if (this.portRmiServer < 0) {
			this.portRmiServer = 0;
		}
		if (this.portRmiRegistry < 0) {
			this.portRmiRegistry = 0;
		}

		this.retryCount = _retryCount;
	}

	/**
	 * Construct RMI Registry URI for service name given using local host address
	 *
	 * @param _serviceName - Name of service to access
	 * @return - string in "//host:port/serviceName" format
	 */
	private String getRmiRegistryLine(String _serviceName) {
		InetSocketAddress isa = null;
		try {
			isa = new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), this.portRmiRegistry);
		} catch (java.net.UnknownHostException e) {
			e.printStackTrace();
		}

		return this.getRmiRegistryLine(isa, _serviceName);
	}

	/**
	 * Construct RMI Registry URI for given service address and name
	 *
	 * @param _isa - address object (host: port)
	 * @param _serviceName - service name
	 * @return - string in "//host:port/serviceName" format
	 */
	protected String getRmiRegistryLine(InetSocketAddress _isa, String _serviceName) {
		assert (_isa != null) : "getRmiRegistryLine InetSocketAddress _isa is NULL!";
		return "//" + _isa.getAddress().getHostAddress() + ":" + _isa.getPort() + "/" + _serviceName;
	}

	/**
	 *
	 * @return - 
	 */
	public boolean moduleStart() {
		try {
			this.initRMIServer();
			return true;
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * @return <b>True</b> if server for remote connections was successfuly stopped.
	 */
	public boolean moduleStop() {
		try {
			if (this.isModuleRunning()) {
				Naming.unbind(this.getRmiRegistryLine(this.serviceName));
			}
			return true;
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (NotBoundException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * @return <b>True</b> if server for remote connections is active
	 */
	public boolean isModuleRunning() {
		try {
			Remote lookup = Naming.lookup(this.getRmiRegistryLine(this.serviceName));
			if (lookup != null) {
				return true;
			}
		} catch (ConnectException _ex) {
			return false;
		} catch (NotBoundException _ex) {
			_ex.printStackTrace();
		} catch (RemoteException _ex) {
			_ex.printStackTrace();
		} catch (MalformedURLException _ex) {
			_ex.printStackTrace();
		}
		return false;
	}

	/**
	 * Checks if local registry is running.
	 *
	 * @return <b>true</b> when local registry was found.
	 */
	private boolean isRegistryRunning() {
		try {
			Remote lookup = Naming.lookup(this.getRmiRegistryLine(this.serviceName));
			if (lookup != null) {
				return true;
			}
		} catch (ConnectException _ex) {
			return false;
		} catch (NotBoundException _ex) {
			return true;
		} catch (RemoteException _ex) {
			_ex.printStackTrace();
		} catch (MalformedURLException _ex) {
			_ex.printStackTrace();
		}
		return false;
	}

	/**
	 *
	 * @return -
	 */
	public String moduleStat() {
		return this.getRmiRegistryLine(this.serviceName) + " status: " + this.isModuleRunning();
	}

	/**
	 * Creates connection to remote Core and calling remote module's method
	 *
	 * @param _isa - Socket object to remote server
	 * @param _name - Remote instance name
	 * @param _method - Method to be called
	 * @param _params - Method parameters
	 * @return -  result of method execution
	 * @throws MessageHandlerException -
	 */
	public Object sendMessage(InetSocketAddress _isa, String _name, String _method, Object[] _params) throws MessageHandlerException {
		return this.sendMessage(_isa, _name, _method, _params, this.retryCount);
	}

	/**
	 *
	 * @param _isa -
	 * @param _name -
	 * @param _method -
	 * @param _params -
	 * @param _retryCount -
	 * @return -
	 * @throws MessageHandlerException -
	 */
	public Object sendMessage(InetSocketAddress _isa, String _name, String _method, Object[] _params, int _retryCount) throws MessageHandlerException {
		int numRetry = _retryCount;
		int id = this.getRequestId();
		String message = "";
		while (_retryCount >= 0) {
			try {
				return this.sendRemoteMessage(id, _isa, _name, _method, _params);
			} catch (MessageHandlerException _ex) {
				//TODO: LOG with warning level
				if (_ex.getCause() != null && _ex.getCause() instanceof MessageHandlerException) {
					throw new MessageHandlerException("Remote invoke error: "+_isa.toString()+":"+_name+"."+_method+"("+ StringUtils.join(_params) +")", _ex);
				}
				_retryCount--;
				message = _ex.getMessage();
			}
		}
		throw new MessageHandlerException("Remote call to " + _isa.toString() + " method Retry count (" + numRetry + ") exeeded: " + message);
	}

	/**
	 * @throws RemoteException - Error creating or contacting registry
	 * @throws MalformedURLException - Error in service URL format
	 */
	protected void initRMIServer() throws RemoteException, MalformedURLException {
		RMHServer server = new RemoteMessageHandlerServer();
		if (!this.isRegistryRunning()) {
			LocateRegistry.createRegistry(this.portRmiRegistry);
		}
		RMHServer stub;
		stub = (RMHServer) UnicastRemoteObject.exportObject(server, this.portRmiServer);

		Registry registry = LocateRegistry.getRegistry(this.portRmiRegistry);
		registry.rebind(this.serviceName, stub);
	}

	/**
	 *
	 * @param id -
	 * @param _isa -
	 * @param _name -
	 * @param _method -
	 * @param _params -
	 * @return -
	 * @throws MessageHandlerException -
	 */
	private Object sendRemoteMessage(int id, InetSocketAddress _isa, String _name, String _method, Object[] _params) throws MessageHandlerException {
		try {
			Object server = Naming.lookup(this.getRmiRegistryLine(_isa, this.serviceName));
			if (server == null) {
				throw new MessageHandlerException("Unable to find RMI server");
			}
			RMHServer s = (RMHServer) server;
			return s.rmhCall(this.hashCode(), id, _name, _method, _params);
		} catch (MessageHandlerException e) {
			throw new MessageHandlerException("Remote method invocation exception", e);
		} catch (NotBoundException e) {
			throw new MessageHandlerException(e.getMessage());
		} catch (RemoteException e) {
			throw new MessageHandlerException(e.getMessage());
		} catch (MalformedURLException e) {
			throw new MessageHandlerException(e.getMessage());
		}
	}

	/**
	 *
	 * @return -
	 */
	synchronized
	private int getRequestId() {
		if (this.requestId == Integer.MAX_VALUE) {
			this.requestId = Integer.MIN_VALUE;
		}
		return this.requestId++;
	}
}
